package memcache

import (
	"errors"
	"gitee.com/aesoper/cache/factory"
	"github.com/bradfitz/gomemcache/memcache"
	"sync"
	"time"
)

type (
	Cache struct {
		client *memcache.Client
		cfg    Options
		mux    sync.RWMutex
	}

	Options struct {
		Addr []string `mapstructure:"addr"`
	}
)


func (c *Cache) Get(key string) ([]byte, error) {
	if err := c.init(); err != nil {
		return nil, err
	}

	c.mux.RLock()
	defer c.mux.RUnlock()

	item, err := c.client.Get(key)
	if err == nil {
		return item.Value, nil
	}

	return nil, err
}

func (c *Cache) Put(key string, val interface{}, timeout time.Duration) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
	if v, ok := val.([]byte); ok {
		item.Value = v
	} else if str, ok := val.(string); ok {
		item.Value = []byte(str)
	} else {
		return errors.New("val only support string and []byte")
	}

	return c.client.Set(&item)
}

func (c *Cache) Delete(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.client.Delete(key)
}

func (c *Cache) Incr(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	_, err := c.client.Increment(key, 1)
	return err
}

func (c *Cache) Decr(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	_, err := c.client.Decrement(key, 1)
	return err
}

func (c *Cache) IsExist(key string) bool {
	if err := c.init(); err != nil {
		return false
	}
	c.mux.RLock()
	defer c.mux.RUnlock()
	_, err := c.client.Get(key)
	return err == nil
}

func (c *Cache) ClearAll() error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.client.DeleteAll()
}

func (c *Cache) StartAndConfigure(ops ...factory.CacheOption) error {
	c.cfg = Options{}

	for _, op := range ops {
		op(c)
	}

	if len(c.cfg.Addr) == 0 {
		c.cfg.Addr = []string{"localhost:11211"}
	}

	return c.init()
}

func (c *Cache) init() error {
	if c.client == nil {
		c.client = memcache.New(c.cfg.Addr...)
		return c.client.Ping()
	}

	return nil
}

func (c *Cache) Close() error {
	if c.client != nil {
		c.client = nil
	}

	return nil
}

func init() {
	factory.Register("memcache", NewCache)
}

func NewCache() factory.Cache {
	return &Cache{}
}

func WithMemcacheOption(op Options) factory.CacheOption {
	return func(c factory.Cache) {
		if cache, ok := c.(*Cache); ok {
			cache.cfg = op
		}
	}
}
